<!-- components/KeyboardShortcuts/KeyboardShortcuts.vue -->
<template>
  <div ref="root">
    <slot></slot>
  </div>
</template>

<script setup>
import { ref, computed, watch, onMounted, onUnmounted } from 'vue'

const props = defineProps({
  shortcuts: {
    type: Array,
    required: true,
    validator: (value) => value.every(item => item.keys && item.action)
  },
  enabled: {
    type: Boolean,
    default: true
  },
  global: {
    type: Boolean,
    default: false
  },
  separator: {
    type: String,
    default: '+'
  },
  modifierSensitive: {
    type: Boolean,
    default: false
  }
})

const emit = defineEmits(['shortcut-triggered', 'shortcut-failed'])

const root = ref(null)
const isMac = /macintosh|mac os x/i.test(navigator.userAgent)
let currentTarget = null
let isListening = false

const keyAliases = {
  ctrl: 'control',
  control: 'control',
  cmd: 'meta',
  command: 'meta',
  meta: 'meta',
  shift: 'shift',
  alt: 'alt',
  option: 'alt',
  esc: 'escape',
  space: ' ',
  up: 'arrowup',
  down: 'arrowdown',
  left: 'arrowleft',
  right: 'arrowright'
}

const normalizeKey = (key) => {
  const lowerKey = key.toLowerCase()
  return keyAliases[lowerKey] || lowerKey
}

const parseKeys = (keysStr) => {
  return keysStr.split(props.separator)
      .map(k => normalizeKey(k.trim()))
      .map(k => isMac && k === 'control' ? 'meta' : k)
}

const normalizeCombo = (keys, isModifierSensitive) => {
  const modifiers = []
  const others = []

  keys.forEach(key => {
    if (['control', 'meta', 'shift', 'alt'].includes(key)) {
      modifiers.push(key)
    } else {
      others.push(key)
    }
  })

  if (!isModifierSensitive) {
    modifiers.sort()
  }

  return [...modifiers, ...others].join('+')
}

const shortcutMap = computed(() => {
  const map = new Map()
  props.shortcuts.forEach(shortcut => {
    try {
      const keys = parseKeys(shortcut.keys)
      const combo = normalizeCombo(keys, props.modifierSensitive)

      if (map.has(combo)) {
        console.warn(`Shortcut conflict: ${shortcut.keys} conflicts with existing shortcut`)
      }

      map.set(combo, shortcut)
    } catch (e) {
      console.error(`Invalid shortcut: ${shortcut.keys}`, e)
    }
  })
  return map
})

const handleKeyDown = (event) => {
  if (!props.enabled) return

  const pressedModifiers = []
  if (event.ctrlKey) pressedModifiers.push('control')
  if (event.shiftKey) pressedModifiers.push('shift')
  if (event.altKey) pressedModifiers.push('alt')
  if (event.metaKey) pressedModifiers.push('meta')

  const key = normalizeKey(event.key)
  if (['control', 'shift', 'alt', 'meta'].includes(key)) return

  const pressedKeys = [...pressedModifiers, key]
  const combo = normalizeCombo(pressedKeys, props.modifierSensitive)

  const matched = shortcutMap.value.get(combo)
  if (matched) {
    event.preventDefault()
    emit('shortcut-triggered', { keys: matched.keys, action: matched.action })
    if (typeof matched.action === 'function') {
      matched.action()
    }
  } else {
    const userCombo = pressedKeys.join(props.separator)
    emit('shortcut-failed', { keys: userCombo })
  }
}

const updateEventListener = () => {
  if (isListening) {
    currentTarget.removeEventListener('keydown', handleKeyDown)
    isListening = false
  }

  if (props.enabled) {
    currentTarget = props.global ? window : root.value
    if (currentTarget) {
      currentTarget.addEventListener('keydown', handleKeyDown)
      isListening = true
    }
  }
}

onMounted(updateEventListener)
onUnmounted(() => {
  if (isListening) {
    currentTarget.removeEventListener('keydown', handleKeyDown)
  }
})

watch([() => props.enabled, () => props.global], updateEventListener)
</script>